package it.polito.connectedcar.message;

import it.polito.connectedcar.entity.InductiveLoopInfo;
import it.polito.connectedcar.tcc.TCCTrafficLightPhaseChange;
import it.polito.connectedcar.tcc.zone.Token;
import it.polito.connectedcar.tcc.zone.ZoneToken;
import it.polito.connectedcar.util.Constants;
import it.polito.connectedcar.vehicle.VehicleZone;

import java.awt.geom.Point2D;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class is used to extract and unpack the data coming from a TCC and
 * directed to a vehicle. It's the counterpart of {@lin TCCToVehicleMessage}
 * 
 * @author Nicola Aresta
 * 
 */
public class VehicleFromTCCMessage extends IncomingMessage {

	// private VehicleIdentificationToken vit;

	private TCCTrafficLightPhaseChange trafficLightPhaseChange;
	private List<InductiveLoopInfo> inductiveLoopsInfo;
	private List<VehicleZone> zonesToAdd;
	private Token changedToken;
	private Map<Token, List<InetSocketAddress>> vehiclesToAdd;

	public VehicleFromTCCMessage(byte[] data) {
		super(data, Constants.VEHICLE_TYPE_CODE);
	}

	@Override
	protected void convertFromData(Storage st) {
		// TODO Auto-generated method stub

	}

	@Override
	protected boolean onProcessMessage(Storage st) {

		try {
			if (sourceTypeCode != Constants.TCC_TYPE_CODE)
				return false;

			switch (actionCode) {
				case Constants.TCC_VEHICLES_LIST_MESSAGE_CODE :
					/* add vehicles to already existing tokens */
					int numToken = st.readByte();
					vehiclesToAdd = new HashMap<Token, List<InetSocketAddress>>();
					for (int k = 0; k < numToken; k++) {
						byte[] token = new byte[TOKEN_LENGTH];
						for (int i = 0; i < TOKEN_LENGTH; i++)
							token[i] = (byte) st.readByte();
						Token t = new ZoneToken(token);

						int numVehicles = st.readInt();

						List<InetSocketAddress> vehicles = new ArrayList<InetSocketAddress>();
						for (int i = 0; i < numVehicles; i++) {
							byte[] address = new byte[4];
							for (int j = 0; j < 4; j++)
								address[j] = (byte) st.readByte();

							int port = st.readInt();
							InetSocketAddress vehicleAddress = new InetSocketAddress(InetAddress.getByAddress(address), port);

							vehicles.add(vehicleAddress);
						}

						vehiclesToAdd.put(t, vehicles);
						// FIXME the token has to be validated

						/**/
					}

					break;
				case Constants.TCC_CHANGE_TOKEN_MESSAGE_CODE :

					/* remove the token */
					byte[] token = new byte[TOKEN_LENGTH];
					for (int i = 0; i < TOKEN_LENGTH; i++)
						token[i] = (byte) st.readByte();

					Token t = new ZoneToken(token);
					changedToken = t;
					break;

				case Constants.TCC_NEW_ZONE_MESSAGE_CODE :

					/* add new tokens */

					numToken = st.readInt();
					zonesToAdd = new ArrayList<VehicleZone>();
					for (int k = 0; k < numToken; k++) {

						token = new byte[st.readInt()];
						for (int i = 0; i < token.length; i++)
							token[i] = (byte) st.readByte();

						t = new ZoneToken(token);

						VehicleZone vz = new VehicleZone();
						vz.setZoneToken(t);

						byte[] address = new byte[st.readByte()];
						for (int j = 0; j < address.length; j++)
							address[j] = (byte) st.readByte();

						int port = st.readInt();
						InetSocketAddress tccAddress = new InetSocketAddress(
								InetAddress.getByAddress(address), port);
						vz.setTccAddress(tccAddress);
						zonesToAdd.add(vz);
						// FIXME the token has to be validated
					}

					break;
				case Constants.TCC_TRAFFIC_LIGHT_UPDATE :

					trafficLightPhaseChange = new TCCTrafficLightPhaseChange();
					trafficLightPhaseChange.setCurrentState(st.readStringASCII());
					trafficLightPhaseChange.setNextState(st.readStringASCII());
					trafficLightPhaseChange.setLastChangeTimeStamp(st.readLong());
					trafficLightPhaseChange.setAssumedTimeToSwitch(st.readLong());
					trafficLightPhaseChange.setPosition(new Point2D.Double(st.readDouble(), st.readDouble()));
					break;
				default :
					return false;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}

		return true;
	}

	public enum TCCMessageEntityType {
		TRAFFIC_LIGHT(0), INDUCTIVE_LOOP(1), ZONES(2);

		private final int code;

		private TCCMessageEntityType(int code) {
			this.code = code;
		}

		public int getCode() {
			return this.code;
		}

		public static TCCMessageEntityType fromCode(int code) throws Exception {
			for (TCCMessageEntityType type : TCCMessageEntityType.values()) {
				if (type.getCode() == code)
					return type;
			}
			throw new Exception(
					"no such TCCMessageEntityType exists for the specified code");
		}
	}

	public enum TCCMessageAttributeType {
		VEHICLES(TCCMessageEntityType.ZONES, 0x01), TRAFFIC_LIGHT(
				TCCMessageEntityType.TRAFFIC_LIGHT, 0x02), INDUCTIVE_LOOP(
				TCCMessageEntityType.INDUCTIVE_LOOP, 0x03), NEXT_HOP_TOKEN(
				TCCMessageEntityType.ZONES, 0x04), CHANGE_TOKEN(
				TCCMessageEntityType.ZONES, 0x05);

		private final int code;
		private final TCCMessageEntityType entityType;

		private TCCMessageAttributeType(TCCMessageEntityType entityType,
				int code) {
			this.entityType = entityType;
			this.code = code;
		}

		public TCCMessageEntityType getEntityType() {
			return this.entityType;
		}

		public int getCode() {
			return this.code;
		}

		public static TCCMessageAttributeType fromEntityTypeAndCode(
				TCCMessageEntityType entityType, int code) throws Exception {
			for (TCCMessageAttributeType mat : TCCMessageAttributeType.values()) {
				if (mat.entityType == entityType && mat.code == code)
					return mat;
			}
			throw new Exception(
					"no such TCCMessageAttributeType exists for the specified EntityType and code");
		}
	}

	public List<VehicleZone> getZonesToAdd() {
		return zonesToAdd;
	}

	public void setZonesToAdd(List<VehicleZone> zonesToAdd) {
		this.zonesToAdd = zonesToAdd;
	}

	public Map<Token, List<InetSocketAddress>> getVehiclesToAdd() {
		return vehiclesToAdd;
	}

	public void setVehiclesToAdd(
			Map<Token, List<InetSocketAddress>> vehiclesToAdd) {
		this.vehiclesToAdd = vehiclesToAdd;
	}

	public TCCTrafficLightPhaseChange getTrafficLightsPhaseChange() {
		return trafficLightPhaseChange;
	}

	public void setTrafficLightsInfo(
			TCCTrafficLightPhaseChange trafficLightPhaseChange) {
		this.trafficLightPhaseChange = trafficLightPhaseChange;
	}

	public List<InductiveLoopInfo> getInductiveLoopsInfo() {
		return inductiveLoopsInfo;
	}

	public void setInductiveLoopsInfo(List<InductiveLoopInfo> inductiveLoopsInfo) {
		this.inductiveLoopsInfo = inductiveLoopsInfo;
	}

	public Token getChangedToken() {
		return changedToken;
	}

	public void setChangedToken(Token changedToken) {
		this.changedToken = changedToken;
	}

}
